package com.didi.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.didi.gulimall.common.utils.DateUtil;
import com.didi.gulimall.common.utils.PageUtils;
import com.didi.gulimall.common.utils.Query;
import com.didi.gulimall.product.dao.CategoryDao;
import com.didi.gulimall.product.entity.CategoryEntity;
import com.didi.gulimall.product.service.CategoryBrandRelationService;
import com.didi.gulimall.product.service.CategoryService;
import com.didi.gulimall.product.vo.Catelog2Vo;
import com.didi.gulimall.product.vo.CatelogVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author 10626
 */
@Service("categoryService")
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Resource
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );
        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> getListWithTree() {
        List<CategoryEntity> entities = baseMapper.selectList(null);
        return entities.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == 0;
        }).peek((menu) ->
                menu.setChildren(getChildren(menu, entities))
        ).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
    }

    @Override
    public List<Long> findCateLogPath(Long catelogId) {
        List<Long> cateLogPath = new ArrayList<>();
        cateLogPath.add(catelogId);
        List<Long> longs = recursiveSearch(catelogId, cateLogPath);
        Collections.reverse(longs);
        return longs;
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = "catalog", key = "'getLevelCategoryOne'"),
            @CacheEvict(value = "catalog", key = "'majorizationGetCatalogJson'")
    })
    @Transactional(rollbackFor = Exception.class)
    public void updateCategory(CategoryEntity category) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-catalog");
        RLock rLock = readWriteLock.writeLock();
        boolean flg = false;
        try {
            flg = rLock.tryLock(2, 120, TimeUnit.SECONDS);
            if (!flg) {
                throw new RuntimeException("您前面正在有人修改数据请等待");
            }
            log.info("加写锁成功");
            Thread.sleep(5500);
            Thread.sleep(5500);
            this.updateById(category);
            if (StringUtils.isNotBlank(category.getName())) {
                categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
            }
        } catch (Exception e) {
//            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (flg) {
                rLock.unlock();
                log.info("写锁释放");
            }
        }
    }

    @Override
    @Cacheable(cacheNames = "catalog", key = "#root.methodName", sync = false)
    public List<CategoryEntity> getLevelCategoryOne() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-catalog");
        RLock rLock = readWriteLock.readLock();
        rLock.lock(10, TimeUnit.SECONDS);
        try {
            log.info("加读锁");
            QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_cid", 0);
            return baseMapper.selectList(queryWrapper);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("读锁释放");
            rLock.unlock();
        }
        return null;
    }

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson2() {
        List<CategoryEntity> categoryList = this.baseMapper.selectList(null);

        List<CategoryEntity> levelCategoryOne = getCategoryEntities(categoryList, 0L);
        return levelCategoryOne.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            List<CategoryEntity> categoryEntities = getCategoryEntities(categoryList, v.getCatId());
            List<Catelog2Vo> catelog2Vos = new ArrayList<>();
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(categoryTwos -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(categoryTwos.getParentCid().toString(), null, categoryTwos.getCatId().toString(), categoryTwos.getName());
                    List<CategoryEntity> categoryThrees = getCategoryEntities(categoryList, categoryTwos.getCatId());
                    List<Catelog2Vo.Catelog3Vo> catelog3Vos = null;
                    if (categoryThrees != null) {
                        catelog3Vos = categoryThrees.stream().map(categoryThree -> {
                            return new Catelog2Vo.Catelog3Vo(categoryTwos.getCatId().toString(), categoryThree.getCatId().toString(), categoryThree.getName());
                        }).collect(Collectors.toList());
                    }
                    catelog2Vo.setCatalog3List(catelog3Vos);
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
    }

    private List<CategoryEntity> getCategoryEntities(List<CategoryEntity> categoryList, Long catId) {
        return categoryList.stream().filter(categoryEntity -> categoryEntity.getCatId().equals(catId)).collect(Collectors.toList());
    }

    @Override
    @Cacheable(cacheNames = "catalog", key = "#root.methodName")
    public Map<String, List<CatelogVo>> majorizationGetCatalogJson() {
        log.info("查询数据库{}", Thread.currentThread().getId());
        List<CatelogVo> catelog2VoList = this.baseMapper.getCatalogJson();
        Map<String, CatelogVo> map = catelog2VoList.stream().collect(Collectors.toMap(CatelogVo::getId, catelogVo -> catelogVo));
        List<CatelogVo> catelogVos = new ArrayList<>();
        for (CatelogVo catelogVo : catelog2VoList) {
            if ("0".equals(catelogVo.getParentId())) {
                catelogVos.add(catelogVo);
            } else {
                CatelogVo father = map.get(catelogVo.getParentId());
                father.getChild().add(catelogVo);
            }
        }
        return catelogVos.stream().collect(Collectors.toMap(CatelogVo::getId, CatelogVo::getChild));
    }

    //    @Override
//    public Map<String, List<CatelogVo>> majorizationGetCatalogJson() {
//        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//        if (StringUtils.isEmpty(catalogJson)) {
//            return getCatalogJsonWithRedisson();
//        }
//        return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CatelogVo>>>() {
//        });
//    }
    private Map<String, List<CatelogVo>> getCatalogJsonWithRedisson() {
        RLock lock = redissonClient.getLock("catalogJson-lock");
        try {
//            lock.tryLock()
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Map<String, List<CatelogVo>> getCatalogJsonRedisLocal() {
        String uuid = UUID.randomUUID().toString();
        Boolean lok = redisTemplate.opsForValue().setIfAbsent("lok", uuid, 30, TimeUnit.SECONDS);
        if (lok) {
            // 站锁成功
            log.info("获取分布式锁成功...");
            Map<String, List<CatelogVo>> getcatalogjsonfromdb = null;
            try {
                getcatalogjsonfromdb = getcatalogjsonfromdb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Long lok1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList("lok"), uuid);
            }
            return getcatalogjsonfromdb;
        } else {
            log.info("获取分布式锁失败...等待重试");
            try {
                Thread.sleep(200);
            } catch (Exception e) {

            }
            return getCatalogJsonRedisLocal();
        }
    }

    private Map<String, List<CatelogVo>> getCatalogJsonWithLocal() {
        synchronized (this) {
            //得到锁以后，我们应该再去缓存中确定一次，如果没有才需要继续查询
            return getcatalogjsonfromdb();
        }
    }

    private Map<String, List<CatelogVo>> getcatalogjsonfromdb() {
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (StringUtils.isNotEmpty(catalogJson)) {
            // 不为空
            return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CatelogVo>>>() {
            });
        }
        log.info("查询数据库{}", Thread.currentThread().getId());
        List<CatelogVo> catelog2VoList = this.baseMapper.getCatalogJson();
        Map<String, CatelogVo> map = catelog2VoList.stream().collect(Collectors.toMap(CatelogVo::getId, catelogVo -> catelogVo));
        List<CatelogVo> catelogVos = new ArrayList<>();
        for (CatelogVo catelogVo : catelog2VoList) {
            if ("0".equals(catelogVo.getParentId())) {
                catelogVos.add(catelogVo);
            } else {
                CatelogVo father = map.get(catelogVo.getParentId());
                father.getChild().add(catelogVo);
            }
        }
        Map<String, List<CatelogVo>> collect = catelogVos.stream().collect(Collectors.toMap(CatelogVo::getId, CatelogVo::getChild));
        String json = JSON.toJSONString(collect);
        redisTemplate.opsForValue().set("catalogJson", json, DateUtil.cacheTime(System.currentTimeMillis()), TimeUnit.MILLISECONDS);
        return collect;
    }

    private List<Long> recursiveSearch(Long catelogId, List<Long> cateLogPath) {
        CategoryEntity category = baseMapper.selectById(catelogId);
        if (category != null && (category.getParentCid().intValue() != 0)) {
            cateLogPath.add(category.getParentCid());
            recursiveSearch(category.getParentCid(), cateLogPath);
        } else {
            return cateLogPath;
        }
        return cateLogPath;
    }

    public List<CategoryEntity> getListWithTreeByMap() {
        List<CategoryEntity> entities = baseMapper.selectList(null);
        List<CategoryEntity> root = new ArrayList<>();
        Map<Long, CategoryEntity> map = new HashMap<>();
        for (CategoryEntity entity : entities) {
            map.put(entity.getCatId(), entity);
        }
        for (CategoryEntity entity : entities) {
            if (entity.getParentCid() == 0) {
                root.add(entity);
            } else {
                CategoryEntity categoryEntity = map.get(entity.getParentCid());
                categoryEntity.getChildren().add(entity);
            }
        }
        return root;
    }

    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> entities) {
        return entities.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).peek((menu) -> menu.setChildren(getChildren(menu, entities))).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
    }

}